package br.mikhas.refractor.method;

import java.lang.reflect.Method;

import br.mikhas.refractor.ReflectionException;
import br.mikhas.refractor.RefractionUtils;

public class AmbiguousMethodProxyImpl implements AmbiguousMethodProxy {

	private final String name;
	private final Class<?> clazz;
	private Object object;

	public AmbiguousMethodProxyImpl(Class<?> clazz, String name) {
		this.name = name;
		this.clazz = clazz;
	}

	public AmbiguousMethodProxyImpl(Class<?> clazz, String name, Object object) {
		this.name = name;
		this.clazz = clazz;
		this.object = object;
	}

	public MethodProxy withArgs(Class<?>... classes) {
		try {
			Method method = RefractionUtils.lookupMethod(this.clazz, name,
					classes);

			if (method == null) {
				throw new ReflectionException("Method not found: " + this.name
						+ '(' + RefractionUtils.typeNames(classes) + ')');
			}

			if (object == null) {
				return new ClassMethodProxy(clazz, method);
			} else {
				return new ObjectMethodProxy(clazz, method, object);
			}
		} catch (SecurityException e) {
			throw new ReflectionException("Permission denied to method "
					+ this.name + RefractionUtils.typeNames(classes), e);
		}
	}

	public MethodProxy withoutArgs() {
		try {
			Method method = this.clazz.getDeclaredMethod(name);
			if (object == null) {
				return new ClassMethodProxy(clazz, method);
			} else {
				return new ObjectMethodProxy(clazz, method, object);
			}
		} catch (SecurityException e) {
			throw new ReflectionException("Permission denied to method "
					+ this.name + "()", e);
		} catch (NoSuchMethodException e) {
			throw new ReflectionException("Method not found: " + this.name
					+ "()", e);
		}
	}

	public String name() {
		return this.name;
	}

	public Object invoke(Object... objects) {
		if (objects.length > 0) {
			Class<?>[] types;

			try {
				types = RefractionUtils.getArgumentsTypes(objects);
			} catch (NullPointerException npe) {
				throw new MethodInvocationException(
						"Could not determine the type of all arguments on array",
						npe);
			}

			MethodProxy proxy = this.withArgs(types);
			return proxy.invoke(objects);

		} else {
			MethodProxy proxy = this.withoutArgs();
			return proxy.invoke();
		}
	}
}
